<template>
	<view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
		<!-- #ifndef APP-NVUE -->
		<canvas
			class="lime-echart__canvas"
			v-if="use2dCanvas"
			type="2d"
			:id="canvasId"
			:style="canvasStyle"
			:disable-scroll="isDisableScroll"
			@touchstart="touchStart"
			@touchmove="touchMove"
			@touchend="touchEnd"
		/>
		<canvas
			class="lime-echart__canvas"
			v-else
			:width="nodeWidth"
			:height="nodeHeight"
			:style="canvasStyle"
			:canvas-id="canvasId"
			:id="canvasId"
			:disable-scroll="isDisableScroll"
			@touchstart="touchStart"
			@touchmove="touchMove"
			@touchend="touchEnd"
		/>
		<view class="lime-echart__mask"
			v-if="isPC"
			@mousedown="touchStart"
			@mousemove="touchMove"
			@mouseup="touchEnd"
			@touchstart="touchStart"
			@touchmove="touchMove"
			@touchend="touchEnd">
		</view>
		<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
		<!-- #endif -->
		<!-- #ifdef APP-NVUE -->
		<web-view
			class="lime-echart__canvas"
			:id="canvasId"
			:style="canvasStyle"
			:webview-styles="webviewStyles"
			ref="webview"
			src="/uni_modules/lime-echart/static/uvue.html?v=1"
			@pagefinish="finished = true"
			@onPostMessage="onMessage"
		></web-view>
		<!-- #endif -->
	</view>
</template>

<script>
// @ts-nocheck
// #ifndef APP-NVUE
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
// #endif

// #ifdef APP-NVUE
import {Echarts} from './nvue'
// #endif
import { sleep,base64ToPath } from './utils';
const charts = {}
const echartsObj = {}


/**
 * LimeChart 图表
 * @description 全端兼容的eCharts
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4899

 * @property {String} customStyle 自定义样式
 * @property {String} type 指定 canvas 类型
 * @value 2d 使用canvas 2d，部分小程序支持
 * @value '' 使用原生canvas，会有层级问题
 * @value bottom right	不缩放图片，只显示图片的右下边区域
 * @property {Boolean} isDisableScroll
 * @property {number} beforeDelay = [30]  延迟初始化 (毫秒)
 * @property {Boolean} enableHover PC端使用鼠标悬浮

 * @event {Function} finished 加载完成触发
 */
export default {
	name: 'lime-echart',
	props: {
		// #ifdef MP-WEIXIN || MP-TOUTIAO
		type: {
			type: String,
			default: '2d'
		},
		// #endif
		// #ifdef APP-NVUE
		webviewStyles: Object,
		// hybrid: Boolean,
		// #endif
		customStyle: String,
		isDisableScroll: Boolean,
		isClickable: {
			type: Boolean,
			default: true
		},
		enableHover: Boolean,
		beforeDelay: {
			type: Number,
			default: 30
		},
		landscape: Boolean
	},
	data() {
		return {
			// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
			use2dCanvas: true,
			// #endif
			// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
			use2dCanvas: false,
			// #endif
			ariaLabel: '图表',
			width: null,
			height: null,
			nodeWidth: null,
			nodeHeight: null,
			// canvasNode: null,
			config: {},
			inited: false,
			finished: false,
			file: '',
			platform: '',
			isPC: false,
			isDown: false,
			isOffscreenCanvas: false,
			offscreenWidth: 0,
			offscreenHeight: 0,
		};
	},
	computed: {
		rootStyle() {
			if(this.landscape) {
				return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
			}
		},
		canvasId() {
			return `lime-echart${this._ && this._.uid || this._uid}`
		},
		offscreenCanvasId() {
			return `${this.canvasId}_offscreen`
		},
		offscreenStyle() {
			return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
		},
		canvasStyle() {
			return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
		}
	},
	// #ifndef VUE3
	beforeDestroy() {
		this.clear()
		this.dispose()
		// #ifdef H5
		if(this.isPC) {
			document.removeEventListener('mousewheel', this.mousewheel)
		}
		// #endif
	},
	// #endif
	// #ifdef VUE3
	beforeUnmount() {
		this.clear()
		this.dispose()
		// #ifdef H5
		if(this.isPC) {
			document.removeEventListener('mousewheel', this.mousewheel)
		}
		// #endif
	},
	// #endif
	created() {
		// #ifdef H5
		if(!('ontouchstart' in window)) {
			this.isPC = true
			document.addEventListener('mousewheel', this.mousewheel)
		}
		// #endif
		// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
		// const { platform } = uni.getSystemInfoSync();
		const { platform } = getDeviceInfo();
		this.isPC = /windows/i.test(platform)
		// #endif
		this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
	},
	mounted() {
		this.$nextTick(() => {
			this.$emit('finished')
		})
	},
	methods: {
		// #ifdef APP-NVUE
		onMessage(e) {
			const detail = e?.detail?.data[0] || null;
			const data = detail?.data
			const key = detail?.event
			const options = data?.options
			const event = data?.event
			const file = detail?.file
			if (key == 'log' && data) {
				console.log(data)
			}
			if(event) {
				this.chart.dispatchAction(event.replace(/"/g,''), options)
			}
			if(file) {
        this.file = file
			}
		},
		// #endif
		setChart(callback) {
			if(!this.chart) {
				console.warn(`组件还未初始化，请先使用 init`)
				return
			}
			if(typeof callback === 'function' && this.chart) {
				callback(this.chart);
			}
			// #ifdef APP-NVUE
			if(typeof callback === 'function') {
				this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
			}
			// #endif
		},
		setOption() {
			if (!this.chart || !this.chart.setOption) {
				console.warn(`组件还未初始化，请先使用 init`)
				return
			}
			this.chart.setOption(...arguments);
		},
		showLoading() {
			if(this.chart) {
				this.chart.showLoading(...arguments)
			}
		},
		hideLoading() {
			if(this.chart) {
				this.chart.hideLoading()
			}
		},
		clear() {
			if(this.chart) {
				this.chart.clear()
			}
		},
		dispose() {
			if(this.chart) {
				this.chart.dispose()
			}
		},
		resize(size) {
			if(size && size.width && size.height) {
				this.height = size.height
				this.width = size.width
				if(this.chart) {this.chart.resize(size)}
			} else {
				this.$nextTick(() => {
					uni.createSelectorQuery()
						.in(this)
						.select(`.lime-echart`)
						.boundingClientRect()
						.exec(res => {
							if (res) {
								let { width, height } = res[0];
								this.width = width = width || 300;
								this.height = height = height || 300;
								this.chart.resize({width, height})
							}
						});
				})

			}

		},
		canvasToTempFilePath(args = {}) {
			// #ifndef APP-NVUE
			const { use2dCanvas, canvasId } = this;
			return new Promise((resolve, reject) => {
				const copyArgs = Object.assign({
					canvasId,
					success: resolve,
					fail: reject
				}, args);
				if (use2dCanvas) {
					delete copyArgs.canvasId;
					copyArgs.canvas = this.canvasNode;
				}
				uni.canvasToTempFilePath(copyArgs, this);
			});
			// #endif
			// #ifdef APP-NVUE
			this.file = ''
			this.$refs.webview.evalJs(`canvasToTempFilePath()`);
			return new Promise((resolve, reject) => {
				this.$watch('file', async (file) => {
					if(file) {
						const tempFilePath = await base64ToPath(file)
						resolve(args.success({tempFilePath}))
					} else {
						reject(args.fail({error: ``}))
					}
				})
			})
			// #endif
		},
		async init(echarts, ...args) {
			// #ifndef APP-NVUE
			if(args && args.length == 0 && !echarts) {
				console.error('缺少参数：init(echarts, theme?:string, opts?: object, callback?: function)')
				return
			}
			// #endif
			let theme=null,opts={},callback;

			Array.from(arguments).forEach(item => {
				if(typeof item === 'function') {
					callback = item
				}
				if(['string'].includes(typeof item)) {
					theme = item
				}
				if(typeof item === 'object') {
					opts = item
				}
			})

			if(this.beforeDelay) {
				await sleep(this.beforeDelay)
			}
			let config = await this.getContext();
			// #ifndef APP-NVUE
			setCanvasCreator(echarts, config)
			try {
				this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
				if(typeof callback === 'function') {
					callback(this.chart)
				} else {
					return this.chart
				}
			} catch(e) {
				console.error(e.messges)
				return null
			}
			// #endif
			// #ifdef APP-NVUE
			this.chart = new Echarts(this.$refs.webview)
			this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
			if(callback) {
				callback(this.chart)
			} else {
				return this.chart
			}
			// #endif
		},
		getContext() {
			// #ifdef APP-NVUE
			if(this.finished) {
				return Promise.resolve(this.finished)
			}
			return new Promise(resolve => {
				this.$watch('finished', (val) => {
					if(val) {
						resolve(this.finished)
					}
				})
			})
			// #endif
			// #ifndef APP-NVUE
			return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
				if(res) {
					let dpr = devicePixelRatio
					let {width, height, node} = res
					let canvas;
					this.width = width = width || 300;
					this.height = height = height || 300;
					if(node) {
						const ctx = node.getContext('2d');
						canvas = new Canvas(ctx, this, true, node);
						this.canvasNode = node
					} else {
						// #ifdef MP-TOUTIAO
						dpr = !this.isPC ? devicePixelRatio : 1// 1.25
						// #endif
						// #ifndef MP-ALIPAY || MP-TOUTIAO
						dpr = this.isPC ? devicePixelRatio : 1
						// #endif
						// #ifdef MP-ALIPAY || MP-LARK
						dpr = devicePixelRatio
						// #endif
						// #ifdef WEB
						dpr = 1
						// #endif
						this.rect = res
						this.nodeWidth = width * dpr;
						this.nodeHeight = height * dpr;
						const ctx = uni.createCanvasContext(this.canvasId, this);
						canvas =  new Canvas(ctx, this, false);
					}

					return { canvas, width, height, devicePixelRatio: dpr, node };
				} else {
					return {}
				}
			})
			// #endif
		},
		// #ifndef APP-NVUE
		getRelative(e, touches) {
			let { clientX, clientY } = e
			if(!(clientX && clientY) && touches && touches[0]) {
				clientX = touches[0].clientX
				clientY = touches[0].clientY
			}
			return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
		},
		getTouch(e, touches) {
			const {x} = touches && touches[0] || {}
			const touch = x ? touches[0] : this.getRelative(e, touches);
			if(this.landscape) {
				[touch.x, touch.y] = [touch.y, this.height - touch.x]
			}
			return touch;
		},
		touchStart(e) {
			this.isDown = true
			const next = () => {
				const touches = convertTouchesToArray(e.touches)
				if(this.chart) {
					const touch = this.getTouch(e, touches)
					this.startX = touch.x
					this.startY = touch.y
					this.startT = new Date()
					const handler = this.chart.getZr().handler;
					dispatch.call(handler, 'mousedown', touch)
					dispatch.call(handler, 'mousemove', touch)
					handler.processGesture(wrapTouch(e), 'start');
					clearTimeout(this.endTimer);
				}

			}
			if(this.isPC) {
				getRect(`#${this.canvasId}`, {context: this}).then(res => {
					this.rect = res
					next()
				})
				return
			}
			next()
		},
		touchMove(e) {
			if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
			const touches = convertTouchesToArray(e.touches)
			if (this.chart && this.isDown) {
				const handler = this.chart.getZr().handler;
				dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
				handler.processGesture(wrapTouch(e), 'change');
			}

		},
		touchEnd(e) {
			this.isDown = false
			if (this.chart) {
				const touches = convertTouchesToArray(e.changedTouches)
				const {x} = touches && touches[0] || {}
				const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
				if(this.landscape) {
					[touch.x, touch.y] = [touch.y,  this.height - touch.x]
				}
				const handler = this.chart.getZr().handler;
				const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
				dispatch.call(handler, 'mouseup', touch)
				handler.processGesture(wrapTouch(e), 'end');
				if(isClick) {
					dispatch.call(handler, 'click', touch)
				} else {
					this.endTimer = setTimeout(() => {
						dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
						dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
					},50)
				}
			}
		},
		// #endif
		// #ifdef H5
		mousewheel(e){
			if(this.chart) {
				// dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
			}
		}
		// #endif
	}
};
</script>
<style>
.lime-echart {
	position: relative;
	/* #ifndef APP-NVUE */
	width: 100%;
	height: 100%;
	/* #endif */
	/* #ifdef APP-NVUE */
	flex: 1;
	/* #endif */
}
.lime-echart__canvas {
	/* #ifndef APP-NVUE */
	width: 100%;
	height: 100%;
	/* #endif */
	/* #ifdef APP-NVUE */
	flex: 1;
	/* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
	position: absolute;
	width: 100%;
	height: 100%;
	left: 0;
	top: 0;
	z-index: 1;
}
/* #endif */
</style>
